#include "opengl_3.h"


#include <string>
#include <fstream>
#include <iostream>
#include <string.h>
#include <algorithm>
//#include "Models/legoMan/LegoMan.h"
using namespace std;
using namespace glm;


bool* pkeys;


//Globals
GLuint programID;
GLuint MatrixID1;
GLuint MatrixID2;
GLuint MatrixID3;

#ifndef _CAVE_
vector<OModel> models;
#else
#include "cave_ogl.h"
vector<OModel> all_models[CAVE_MAX_INDEXES];
#define models all_models[CAVEUniqueIndex()]
#endif

int map[100][100];




#ifndef _CAVE_
// Initial position : on +Z
//glm::vec3 position = glm::vec3( 5.0f, 30.0f, 5.0f ); 

glm::vec3 positionMove = glm::vec3( 70.0f, 60.0f, 60.0f ); 
glm::vec3 position = glm::vec3( 0.0f, 0.0f, 0.0f ); 
float horizontalAngle = 0.0f;
float verticalAngle = -1.46f;

// Initial Field of View
float initialFoV = 55.0f;

float speed = 0.05f;
float angleSpeed = 0.00001f;
#endif


#ifdef _CAVE_


glm::vec3 positionMove = glm::vec3( 70.0f, 60.0f, 60.0f ); 
glm::vec3 position = glm::vec3( 0.0f, 0.0f, 0.0f ); 
float horizontalAngle = 0.0f;
float verticalAngle = -1.46f;

// Initial Field of View
float initialFoV = 55.0f;

float speed = 0.0001f;
float angleSpeed = 0.01f;

#endif

OpenGLContext::OpenGLContext(void) {

}

#ifndef _CAVE_
/**
  Constructor for the OpenGLContext class which will create a context given a windows HWND.
*/
OpenGLContext::OpenGLContext(HWND hwnd) {
  create30Context(hwnd); // Create a context given a HWND
}
#endif
/**
  Destructor for our OpenGLContext class which will clean up our rendering context
  and release the device context from the current window.
*/
OpenGLContext::~OpenGLContext(void) {
#ifndef _CAVE_
    wglMakeCurrent(hdc, 0); // Remove the rendering context from our device context
    wglDeleteContext(hrc); // Delete our rendering context

    ReleaseDC(hwnd, hdc); // Release the device context from our window
#endif
}
#ifndef _CAVE_
/**
  create30Context creates an OpenGL context and attaches it to the window provided by
  the HWND. This method currently creates an OpenGL 3.2 context by default, but will default
  to an OpenGL 2.1 capable context if the OpenGL 3.2 context cannot be created.
*/
bool OpenGLContext::create30Context(HWND hwnd) {
  this->hwnd = hwnd; // Set the HWND for our window

  hdc = GetDC(hwnd); // Get the device context for our window

  PIXELFORMATDESCRIPTOR pfd; // Create a new PIXELFORMATDESCRIPTOR (PFD)
  memset(&pfd, 0, sizeof(PIXELFORMATDESCRIPTOR)); // Clear our  PFD
  pfd.nSize = sizeof(PIXELFORMATDESCRIPTOR); // Set the size of the PFD to the size of the class
  pfd.dwFlags = PFD_DOUBLEBUFFER | PFD_SUPPORT_OPENGL | PFD_DRAW_TO_WINDOW; // Enable double buffering, opengl support and drawing to a window
  pfd.iPixelType = PFD_TYPE_RGBA; // Set our application to use RGBA pixels
  pfd.cColorBits = 32; // Give us 32 bits of color information (the higher, the more colors)
  pfd.cDepthBits = 32; // Give us 32 bits of depth information (the higher, the more depth levels)
  pfd.iLayerType = PFD_MAIN_PLANE; // Set the layer of the PFD

  int nPixelFormat = ChoosePixelFormat(hdc, &pfd); // Check if our PFD is valid and get a pixel format back
  if (nPixelFormat == 0) // If it fails
      return false;

  bool bResult = SetPixelFormat(hdc, nPixelFormat, &pfd); // Try and set the pixel format based on our PFD
  if (!bResult) // If it fails
    return false;

  HGLRC tempOpenGLContext = wglCreateContext(hdc); // Create an OpenGL 2.1 context for our device context
  wglMakeCurrent(hdc, tempOpenGLContext); // Make the OpenGL 2.1 context current and active

  GLenum error = glewInit(); // Enable GLEW
  if (error != GLEW_OK) // If GLEW fails
    return false;

  int attributes[] = {
    WGL_CONTEXT_MAJOR_VERSION_ARB, 3, // Set the MAJOR version of OpenGL to 3
    WGL_CONTEXT_MINOR_VERSION_ARB, 2, // Set the MINOR version of OpenGL to 2
    WGL_CONTEXT_FLAGS_ARB, WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB, // Set our OpenGL context to be forward compatible
    0
  };

  if (wglewIsSupported("WGL_ARB_create_context") == 1) { // If the OpenGL 3.x context creation extension is available
    hrc = wglCreateContextAttribsARB(hdc, NULL, attributes); // Create and OpenGL 3.x context based on the given attributes
    wglMakeCurrent(NULL, NULL); // Remove the temporary context from being active
    wglDeleteContext(tempOpenGLContext); // Delete the temporary OpenGL 2.1 context
    wglMakeCurrent(hdc, hrc); // Make our OpenGL 3.0 context current
  }
  else {
    hrc = tempOpenGLContext; // If we didn't have support for OpenGL 3.x and up, use the OpenGL 2.1 context
  }

  int glVersion[2] = {-1, -1}; // Set some default values for the version
  glGetIntegerv(GL_MAJOR_VERSION, &glVersion[0]); // Get back the OpenGL MAJOR version we are using
  glGetIntegerv(GL_MINOR_VERSION, &glVersion[1]); // Get back the OpenGL MAJOR version we are using

  std::cout << "Using OpenGL: " << glVersion[0] << "." << glVersion[1] << std::endl; // Output which version of OpenGL we are using

  return true; // We have successfully created a context, return true
}
#endif







GLuint LoadShaders(const char * vertex_file_path,const char * fragment_file_path){

  // Create the shaders
  GLuint VertexShaderID = glCreateShader(GL_VERTEX_SHADER);
  GLuint FragmentShaderID = glCreateShader(GL_FRAGMENT_SHADER);

  // Read the Vertex Shader code from the file
  std::string VertexShaderCode;
  std::ifstream VertexShaderStream(vertex_file_path, std::ios::in);
  if(VertexShaderStream.is_open()){
    std::string Line = "";
    while(getline(VertexShaderStream, Line))
      VertexShaderCode += "\n" + Line;
    VertexShaderStream.close();
  }

  // Read the Fragment Shader code from the file
  std::string FragmentShaderCode;
  std::ifstream FragmentShaderStream(fragment_file_path, std::ios::in);
  if(FragmentShaderStream.is_open()){
    std::string Line = "";
    while(getline(FragmentShaderStream, Line))
      FragmentShaderCode += "\n" + Line;
    FragmentShaderStream.close();
  }



  GLint Result = GL_FALSE;
  int InfoLogLength;



  // Compile Vertex Shader
  printf("Compiling shader : %s\n", vertex_file_path);
  char const * VertexSourcePointer = VertexShaderCode.c_str();
  glShaderSource(VertexShaderID, 1, &VertexSourcePointer , NULL);
  glCompileShader(VertexShaderID);

  // Check Vertex Shader
  glGetShaderiv(VertexShaderID, GL_COMPILE_STATUS, &Result);
  glGetShaderiv(VertexShaderID, GL_INFO_LOG_LENGTH, &InfoLogLength);
  std::vector<char> VertexShaderErrorMessage(InfoLogLength);
  glGetShaderInfoLog(VertexShaderID, InfoLogLength, NULL, &VertexShaderErrorMessage[0]);
  fprintf(stdout, "%s\n", &VertexShaderErrorMessage[0]);




  // Compile Fragment Shader
  printf("Compiling shader : %s\n", fragment_file_path);
  char const * FragmentSourcePointer = FragmentShaderCode.c_str();
  glShaderSource(FragmentShaderID, 1, &FragmentSourcePointer , NULL);
  glCompileShader(FragmentShaderID);

  // Check Fragment Shader
  glGetShaderiv(FragmentShaderID, GL_COMPILE_STATUS, &Result);
  glGetShaderiv(FragmentShaderID, GL_INFO_LOG_LENGTH, &InfoLogLength);
  std::vector<char> FragmentShaderErrorMessage(InfoLogLength);
  glGetShaderInfoLog(FragmentShaderID, InfoLogLength, NULL, &FragmentShaderErrorMessage[0]);
  fprintf(stdout, "%s\n", &FragmentShaderErrorMessage[0]);
  

  // Link the program
  fprintf(stdout, "Linking program\n");
  GLuint ProgramID = glCreateProgram();
  glAttachShader(ProgramID, VertexShaderID);
  glAttachShader(ProgramID, FragmentShaderID);
  glLinkProgram(ProgramID);

  // Check the program
  glGetProgramiv(ProgramID, GL_LINK_STATUS, &Result);
  glGetProgramiv(ProgramID, GL_INFO_LOG_LENGTH, &InfoLogLength);
  std::vector<char> ProgramErrorMessage( InfoLogLength );
  glGetProgramInfoLog(ProgramID, InfoLogLength, NULL, &ProgramErrorMessage[0]);
  fprintf(stdout, "%s\n", &ProgramErrorMessage[0]);
  
  glDeleteShader(VertexShaderID);
  glDeleteShader(FragmentShaderID);

  return ProgramID;
}


GLuint loadBMP_custom(const char * imagepath){

  printf("Reading image %s\n", imagepath);

  // Data read from the header of the BMP file
  unsigned char header[54];
  unsigned int dataPos;
  unsigned int imageSize;
  unsigned int width, height;
  // Actual RGB data
  unsigned char * data;

  // Open the file
  FILE * file = fopen(imagepath,"rb");
  if (!file)							    {printf("Image could not be opened\n"); return 0;}

  // Read the header, i.e. the 54 first bytes

  // If less than 54 byes are read, problem
  if ( fread(header, 1, 54, file)!=54 ){ 
    printf("Not a correct BMP file\n");
    return false;
  }
  // A BMP files always begins with "BM"
  if ( header[0]!='B' || header[1]!='M' ){
    printf("Not a correct BMP file\n");
    return 0;
  }
  // Make sure this is a 24bpp file
  if ( *(int*)&(header[0x1E])!=0  )         {printf("Not a correct BMP file\n");    return 0;}
  if ( *(int*)&(header[0x1C])!=24 )         {printf("Not a correct BMP file\n");    return 0;}

  // Read the information about the image
  dataPos    = *(int*)&(header[0x0A]);
  imageSize  = *(int*)&(header[0x22]);
  width      = *(int*)&(header[0x12]);
  height     = *(int*)&(header[0x16]);

  // Some BMP files are misformatted, guess missing information
  if (imageSize==0)    imageSize=width*height*3; // 3 : one byte for each Red, Green and Blue component
  if (dataPos==0)      dataPos=54; // The BMP header is done that way

  // Create a buffer
  data = new unsigned char [imageSize];

  // Read the actual data from the file into the buffer
  fread(data,1,imageSize,file);

  // Everything is in memory now, the file wan be closed
  fclose (file);

  // Create one OpenGL texture
  GLuint textureID;
  glGenTextures(1, &textureID);
  
  // "Bind" the newly created texture : all future texture functions will modify this texture
  glBindTexture(GL_TEXTURE_2D, textureID);

  // Give the image to OpenGL
  glTexImage2D(GL_TEXTURE_2D, 0,GL_RGB, width, height, 0, GL_BGR, GL_UNSIGNED_BYTE, data);

  // Poor filtering, or ...
  //glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
  //glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); 

  // ... nice trilinear filtering.
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); 
  glGenerateMipmap(GL_TEXTURE_2D);

  // Return the ID of the texture we just created
  return textureID;
}

bool loadOBJ(const char * path, std::vector<glm::vec3> & out_vertices, std::vector<glm::vec2> & out_uvs,std::vector<glm::vec3> & out_normals){
  printf("Loading OBJ file %s...\n", path);

  std::vector<unsigned int> vertexIndices, uvIndices, normalIndices;
  std::vector<glm::vec3> temp_vertices; 
  std::vector<glm::vec2> temp_uvs;
  std::vector<glm::vec3> temp_normals;


  FILE * file = fopen(path, "r");
  if( file == NULL ){
    printf("Impossible to open the file ! Are you in the right path ? See Tutorial 1 for details\n");
    return false;
  }

  while( 1 ){

    char lineHeader[128];
    // read the first word of the line
    int res = fscanf(file, "%s", lineHeader);
    if (res == EOF)
      break; // EOF = End Of File. Quit the loop.

    // else : parse lineHeader
    
    if ( strcmp( lineHeader, "v" ) == 0 ){
      glm::vec3 vertex;
      fscanf(file, "%f %f %f\n", &vertex.x, &vertex.y, &vertex.z );
      temp_vertices.push_back(vertex);
    }else if ( strcmp( lineHeader, "vt" ) == 0 ){
      glm::vec2 uv;
      fscanf(file, "%f %f\n", &uv.x, &uv.y );
      uv.y = -uv.y; // Invert V coordinate since we will only use DDS texture, which are inverted. Remove if you want to use TGA or BMP loaders.
      temp_uvs.push_back(uv);
    }else if ( strcmp( lineHeader, "vn" ) == 0 ){
      glm::vec3 normal;
      fscanf(file, "%f %f %f\n", &normal.x, &normal.y, &normal.z );
      temp_normals.push_back(normal);
    }else if ( strcmp( lineHeader, "f" ) == 0 ){
      std::string vertex1, vertex2, vertex3;
      unsigned int vertexIndex[3], uvIndex[3], normalIndex[3];
      int matches = fscanf(file, "%d/%d/%d %d/%d/%d %d/%d/%d\n", &vertexIndex[0], &uvIndex[0], &normalIndex[0], &vertexIndex[1], &uvIndex[1], &normalIndex[1], &vertexIndex[2], &uvIndex[2], &normalIndex[2] );
      if (matches != 9){
        printf("File can't be read by our simple parser :-( Try exporting with other options\n");
        return false;
      }
      vertexIndices.push_back(vertexIndex[0]);
      vertexIndices.push_back(vertexIndex[1]);
      vertexIndices.push_back(vertexIndex[2]);
      uvIndices    .push_back(uvIndex[0]);
      uvIndices    .push_back(uvIndex[1]);
      uvIndices    .push_back(uvIndex[2]);
      normalIndices.push_back(normalIndex[0]);
      normalIndices.push_back(normalIndex[1]);
      normalIndices.push_back(normalIndex[2]);
    }else{
      // Probably a comment, eat up the rest of the line
      char stupidBuffer[1000];
      fgets(stupidBuffer, 1000, file);
    }

  }

  // For each vertex of each triangle
  for( unsigned int i=0; i<vertexIndices.size(); i++ ){

    // Get the indices of its attributes
    unsigned int vertexIndex = vertexIndices[i];
    unsigned int uvIndex = uvIndices[i];
    unsigned int normalIndex = normalIndices[i];
    
    // Get the attributes thanks to the index
    glm::vec3 vertex = temp_vertices[ vertexIndex-1 ];
    glm::vec2 uv = temp_uvs[ uvIndex-1 ];
    glm::vec3 normal = temp_normals[ normalIndex-1 ];
    
    // Put the attributes in buffers
    out_vertices.push_back(vertex);
    out_uvs     .push_back(uv);
    out_normals .push_back(normal);
  
  }

  return true;
}


#include "physics.h";

void loadMap(std::string mapName) {

 std::ifstream myReadFile; 
 myReadFile.open(mapName); 

  
 if (myReadFile.is_open()) {
   int line = 0;
   while (!myReadFile.eof()) { 
     std::string lineC;
     std::getline( myReadFile, lineC);    

     for (int i = 0; i < MAP_SIZE; i++) {
       const char a = (lineC.c_str())[i];
       map[i][line] = atoi( &a );  
    //   cout << map[i][line] << endl;
     }

     line++;
   }
 }
  
 myReadFile.close();

  
}

void loadModel(std::string textureName, std::string modelName) {
  OModel model;

  model.Texture = loadBMP_custom(textureName.c_str()); 
  model.TextureID  = glGetUniformLocation(programID, "myTextureSampler");
  bool res = loadOBJ(modelName.c_str(), model.vertices, model.uvs, model.normals);

  glGenBuffers(1, &model.vertexbuffer);
  glBindBuffer(GL_ARRAY_BUFFER, model.vertexbuffer);
  glBufferData(GL_ARRAY_BUFFER, model.vertices.size() * sizeof(GLfloat)*3 , &model.vertices[0], GL_STATIC_DRAW);
    
  glGenBuffers(1, &model.uvbuffer);
  glBindBuffer(GL_ARRAY_BUFFER, model.uvbuffer);
  glBufferData(GL_ARRAY_BUFFER, model.uvs.size() * sizeof(GLfloat)*2, &model.uvs[0], GL_STATIC_DRAW);
  models.push_back(model);
}

void OpenGLContext::setupScene(void) {
  
//  glClearColor(0.0f, 0.0f, 0.0f, 0.0f); // Set the clear color based on Microsofts CornflowerBlue (default in XNA)
 
  // Ensure we can capture the escape key being pressed below
 // glfwEnable( GLFW_STICKY_KEYS );
 // glfwSetMousePos(1024/2, 768/2);

  
  glClearColor(0.0f, 0.0f, 0.3f, 0.0f);


    glColor4f(1.0f, 0.0f, 0.0f, 1.0f);
    glEnable(GL_LIGHTING);
    glEnable(GL_LIGHT0);
    glEnable(GL_DEPTH_TEST);

    GLfloat light_amb[4] = {0.4f, 0.4f, 0.4f, 1.0f};
    GLfloat light_dif[4] = {0.0f, 0.0f, 0.0f, 1.0f};
    GLfloat light_spe[4] = {0.5f, 0.5f, 0.5f, 1.0f};
    GLfloat light_pos[4] = {0.0f, 10.0f, 5.0f, 1.0f};
    GLfloat light_dir[4] = {0.0f, 0.0f, 0.0f, 1.0f};
  
    GLfloat material_amd[4] = {1.0f, 0.1f, 0.1f, 1.0f};
    GLfloat material_spe[4] = {0.8f, 0.8f, 0.8f, 1.0f};

    glMaterialfv(GL_FRONT_AND_BACK,  GL_AMBIENT, material_amd);
    glMaterialfv(GL_FRONT_AND_BACK,  GL_DIFFUSE, material_amd);
    glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, material_spe);
    glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, 128);

    glEnable(GL_LIGHT1);
    glLightfv(GL_LIGHT1, GL_POSITION      , light_pos);
    glLightfv(GL_LIGHT1, GL_SPOT_DIRECTION, light_dir);
    glLightfv(GL_LIGHT1, GL_AMBIENT       , light_amb);
    glLightfv(GL_LIGHT1, GL_DIFFUSE       , light_dif);
    glLightfv(GL_LIGHT1, GL_SPECULAR	     , light_spe);
    glLightf (GL_LIGHT1, GL_SPOT_CUTOFF	 , 24);
    glLightf (GL_LIGHT1, GL_SPOT_EXPONENT , 128);



  // Enable depth test
//  glEnable(GL_DEPTH_TEST);
  // Accept fragment if it closer to the camera than the former one
  glDepthFunc(GL_LESS); 

  // Cull triangles which normal is not towards the camera
  glEnable(GL_CULL_FACE);

  GLuint VertexArrayID;
  glGenVertexArrays(1, &VertexArrayID);
  glBindVertexArray(VertexArrayID);

  // Create and compile our GLSL program from the shaders
  programID = LoadShaders( "TransformVertexShader.vertexshader", "TextureFragmentShader.fragmentshader" );

  // Get a handle for our "MVP" uniform
  MatrixID1 = glGetUniformLocation(programID, "u_V");
  MatrixID2 = glGetUniformLocation(programID, "u_M");
  MatrixID3 = glGetUniformLocation(programID, "u_P");

#ifdef NAMESTI
  loadMap("Models/map1.dat");
#endif
#ifndef NAMESTI
  loadMap("Models/map2.dat");
#endif

  loadModel("Models/legoMan/Texture.bmp", "Models/legoMan/LegoMan.obj"); //0
  loadModel("Models/legoMan2/Texture.bmp", "Models/legoMan2/LegoMan.obj"); //1
  loadModel("Models/legoMan3/Texture.bmp", "Models/legoMan3/LegoMan.obj"); //2
  loadModel("Models/legoMan4/Texture.bmp", "Models/legoMan4/LegoMan.obj"); //3
  loadModel("Models/grass/grass.bmp", "Models/grass/grass.obj"); //4
  loadModel("Models/cube/cube.bmp", "Models/cube/cube.obj"); //5
  loadModel("Models/check/grass.bmp", "Models/check/grass.obj"); //6

}

#ifndef _CAVE_
void OpenGLContext::reshapeWindow(int w, int h) {
  windowWidth = w; // Set the window width
  windowHeight = h; // Set the window height
   
}
#endif





glm::mat4 OpenGLContext::getViewMatrix() const {
  return ViewMatrix;
}
glm::mat4 OpenGLContext::getProjectionMatrix() const{
  return ProjectionMatrix;
}

void OpenGLContext::setViewMatrix(const glm::mat4 mat)
{
  ViewMatrix = mat;
}
void OpenGLContext::setProjectionMatrix(const glm::mat4 mat)
{
  ProjectionMatrix = mat;
}


#ifndef _CAVE_
void OpenGLContext::computeMatricesFromInputs(){
  // glfwGetTime is called only once, the first time this function is called
  static double lastTime = clock();

  // Compute time difference between current and last frame
  double currentTime = clock();
  float deltaTime = float(currentTime - lastTime);

  // Get mouse position
  int xpos, ypos;
 //TD glfwGetMousePos(&xpos, &ypos);

  // Reset mouse position for next frame
 //TD glfwSetMousePos(1024/2, 768/2);

  // Compute new orientation
//TD  horizontalAngle += mouseSpeed * float(1024/2 - xpos );
 //TD verticalAngle   += mouseSpeed * float( 768/2 - ypos );

  // Direction : Spherical coordinates to Cartesian coordinates conversion
  glm::vec3 direction(
    cos(verticalAngle) * sin(horizontalAngle), 
    sin(verticalAngle),
    cos(verticalAngle) * cos(horizontalAngle)
  );
  
  // Right vector
  glm::vec3 right = glm::vec3(
    sin(horizontalAngle - 3.14f/2.0f), 
    0,
    cos(horizontalAngle - 3.14f/2.0f)
  );
  
  // Up vector
  glm::vec3 up = glm::cross( right, direction );
  
  if (pkeys['W'] == true )
  {
     position += direction * deltaTime * speed;
  }

  if (pkeys['S'] == true )
  {
    position -= direction * deltaTime * speed;
  }

  if (pkeys['D'] == true )
  {
    position += right * deltaTime * speed;
  }

  if (pkeys['A'] == true )
  {
    position -= right * deltaTime * speed;
  }

  if (pkeys[VK_UP] == true )
  {
    verticalAngle += angleSpeed;
  }

  if (pkeys[VK_DOWN] == true )
  {
    verticalAngle -= angleSpeed;
  }

  if (pkeys[VK_LEFT] == true )
  {
    horizontalAngle += angleSpeed;
  }

  if (pkeys[VK_RIGHT] == true )
  {
    horizontalAngle -= angleSpeed;
  }
  
  float FoV = initialFoV;// * glfwGetMouseWheel();

  // Projection matrix : 45 Field of View, 4:3 ratio, display range : 0.1 unit <-> 100 units
   glm::mat4 ProjectionM = glm::perspective(FoV, 4.0f / 3.0f, 0.1f, 5000.0f);
  // Camera matrix
   glm::mat4 ViewM       = glm::lookAt(
                position,           // Camera is here
                position+direction, // and looks here : at the same position, plus "direction"
                up                  // Head is up (set to 0,-1,0 to look upside-down)
               );

  setProjectionMatrix ( ProjectionM );
  setViewMatrix ( ViewM );

  // For the next frame, the "last time" will be "now"
  lastTime = currentTime;
}
#endif


void OpenGLContext::updateCam(int idBtn) {

// P>U 3
// P>L 0
// P>R 2
// P>B 1
  
// DP 4
// DZ 6
 
  // glfwGetTime is called only once, the first time this function is called
  static double lastTime = clock();

  // Compute time difference between current and last frame
  double currentTime = clock();
  float deltaTime = float(currentTime - lastTime);

  // Get mouse position
  int xpos, ypos;
 
  
 


  if (idBtn == 3 )
  {
    verticalAngle += angleSpeed;
  }

  if (idBtn == 1 )
  {
    verticalAngle -= angleSpeed;
    //std::cout << "1" << std::endl;
  }

  if (idBtn == 0 )
  {
    horizontalAngle += angleSpeed;
  }

  if (idBtn == 2 )
  {
    horizontalAngle -= angleSpeed;
  }
  
// Direction : Spherical coordinates to Cartesian coordinates conversion
  glm::vec3 direction(
    cos(verticalAngle) * sin(horizontalAngle), 
    sin(verticalAngle),
    cos(verticalAngle) * cos(horizontalAngle)
  );

  // Right vector
  glm::vec3 right = glm::vec3(
    sin(horizontalAngle - 3.14f/2.0f), 
    0,
    cos(horizontalAngle - 3.14f/2.0f)
  );

  // Up vector
  glm::vec3 up = glm::cross( right, direction );
  
  if (idBtn == 4 )
  {
     position += direction * deltaTime * speed;
    //std::cout << "4" << std::endl;
  }

  if (idBtn == 6 )
  {
    position -= direction * deltaTime * speed;
  }

  //float FoV = initialFoV;// * glfwGetMouseWheel();

  // Projection matrix : 45 Field of View, 4:3 ratio, display range : 0.1 unit <-> 100 units
  //ProjectionMatrix = glm::perspective(FoV, 4.0f / 3.0f, 0.1f, 5000.0f);
  // Camera matrix
  glm::mat4 CaveView = getViewMatrix();

  glm::vec3 posN = glm::vec3( 0.0f, 0.0f, 0.0f ); 

  glm::mat4 VMatrix = glm::lookAt(
                posN,           // Camera is here
                posN+direction, // and looks here : at the same position, plus "direction"
                up                  // Head is up (set to 0,-1,0 to look upside-down)
               );

  // For the next frame, the "last time" will be "now"
  VMatrix =  CaveView * VMatrix;// * VMatrix;// * glm::mat4(1.0,0.0,0.0,0.0, 0.0,1.0,0.0,0.0, 0.0,0.0,1.0,0.0, position.x,position.y,position.z,1.0)  ;
    

  setViewMatrix (   VMatrix); // * glm::mat4(1.0,0.0,0.0,0.0, 0.0,1.0,0.0,0.0, 0.0,0.0,1.0,0.0, position.x,position.y,position.z,1.0));
 // setViewMatrix (    glm::mat4(1.0,0.0,0.0,0.0, 0.0,1.0,0.0,0.0, 0.0,0.0,1.0,0.0, 0.0f,0.0f, 0.0f ,1.0) * VMatrix);

  lastTime = currentTime;
}

 
void OpenGLContext::drawModel(OModel& model, DrawModel p) {
  
   // Bind our texture in Texture Unit 0
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, model.Texture);
    // Set our "myTextureSampler" sampler to user Texture Unit 0
    glUniform1i(model.TextureID, 0);
    
    // Compute the MVP matrix from keyboard and mouse input
    #ifndef _CAVE_
    computeMatricesFromInputs();
    #endif
    glm::mat4 ProjectionMatrix = getProjectionMatrix();
    glm::mat4 ViewMatrix = getViewMatrix();
   
    glm::mat4 ModelMatrix = glm::mat4(1.0,0.0,0.0,0.0, 0.0,1.0,0.0,0.0, 0.0,0.0,1.0,0.0, p.x-positionMove.x,p.z-positionMove.y,p.y-positionMove.z,1.0) * 
                            glm::mat4(cos(p.a),0.0,-sin(p.a),0.0, 0.0,1.0,0.0,0.0, sin(p.a),0.0,cos(p.a),0.0, 0.0,0.0,0.0,1.0);
    //glm::mat4 MV = ViewMatrix * ModelMatrix;
   // glm::mat4 MVP = ProjectionMatrix * ViewMatrix * ModelMatrix;

    // Send our transformation to the currently bound shader, 
    // in the "MVP" uniform
    glUniformMatrix4fv(MatrixID1, 1, GL_FALSE, &ModelMatrix[0][0]);
    glUniformMatrix4fv(MatrixID2, 1, GL_FALSE, &ViewMatrix[0][0]);
    glUniformMatrix4fv(MatrixID3, 1, GL_FALSE, &ProjectionMatrix[0][0]);


    // 1rst attribute buffer : vertices
    glEnableVertexAttribArray(0);
    glBindBuffer(GL_ARRAY_BUFFER, model.vertexbuffer);
    glVertexAttribPointer(
      0,                  // attribute
      3,                  // size
      GL_FLOAT,           // type
      GL_FALSE,           // normalized?
      0,                  // stride
      (void*)0            // array buffer offset
    );

    // 2nd attribute buffer : UVs
    glEnableVertexAttribArray(1);
    glBindBuffer(GL_ARRAY_BUFFER, model.uvbuffer);
    glVertexAttribPointer(
      1,                                // attribute
      2,                                // size
      GL_FLOAT,                         // type
      GL_FALSE,                         // normalized?
      0,                                // stride
      (void*)0                          // array buffer offset
    );

    // Draw the triangle !
    glDrawArrays(GL_TRIANGLES, 0, model.vertices.size() );
   
    glDisableVertexAttribArray(0);
    glDisableVertexAttribArray(1);

}

void OpenGLContext::renderScene(void) {
    // Clear the screen
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    // Use our shader
    glUseProgram(programID);
    
    for (int i = 0; i < signed(a_peons.size()); i++) {
      DrawModel p;
      p.x = a_peons[i].x; p.y = a_peons[i].y; p.z = a_peons[i].z; p.a = a_peons[i].realAngle; p.idM = a_peons[i].model;
      drawModel(models[p.idM], p);
    }
    
    for (int i = 0; i < signed(envModels.size()); i++) {      
      drawModel(models[envModels[i].idM], envModels[i]);
    }

#ifndef _CAVE_
  SwapBuffers(hdc); // Swap buffers so we can see our rendering
#endif
}
#ifndef _CAVE_
void OpenGLContext::updateKeyboard(bool* key){
  pkeys = key;
}
#endif
